<html>
    <head>
        <title>异常处理</title>
    </head>
    <body>
        <script>
            // 异常处理  Exception Handling  

                /*
                    ABP 提供了一个内置的基础设施，并提供了一个处理异常的标准模型。

                        ABP自动处理所有异常，并向客户端发送"标准格式的错误消息"以获取API/AJAX请求。

                        自动隐藏内部基础架构错误，并返回标准错误信息。

                        提供一种简单且可配置的方法来"本地化异常消息"

                        自动将"标准异常"映射到 Http状态码，并提供可配置的选项来"映射自定义异常"
                */

                /*
                    一、自动异常处理

                        1、进行异常处理的条件

                                AbpExceptionFilter 如果满足以下任何条件，则处理异常：

                                    ①、 控制器操作引发异常，该操作返回对象结果(不是视图结果);

                                    ②、 该请求是一个Ajax请求 （X-Requested-With Http标头值为 XMLHttpRequest）

                                    ③、 客户端显式接受application/json内容类型（通过acceptHTTP标头）。

                                如果处理了异常，它会自动记录并将格式化的JSON消息返回给客户端。

                        2、错误消息格式
                                
                                    错误消息是 "RemoteServiceErrorResponse" 类的一个实例。
                                    
                                    最简单的错误JSON只有一个消息属性，如下所示：

                                    {
                                        "error": {
                                            "message": "This topic is locked and can not add a new message"
                                        }
                                    }

                                    "可选字段" 可以根据发生的异常来填充、

                                   ①、 错误代码 ErrorCode
                                          Errorcode是异常的可选且唯一的字符串值。

                                          抛出的Exception应实现IHasErrorCode接口来填充此字段。示例JSON值：

                                                {
                                                    "error": {
                                                        "code": "App:010042",
                                                        "message": "This topic is locked and can not add a new message"
                                                    }
                                                }

                                    ②、错误详情 Error Details
                                            错误详情在Json错误消息的可选字段中。
                                            抛出Exception应该实现 IHasErrorDetails接口来填充此字段：
                                                {
                                                    "error": {
                                                        "code": "App:010042",
                                                        "message": "This topic is locked and can not add a new message",
                                                        "details": "A more detailed info about the error..."
                                                    }
                                                }

                                    ③、验证错误
                                            如果抛出的异常实现了IHasValidationErrors接口，则会填充一个标准字段。
                                                {
                                                    "error": {
                                                        "code": "App:010046",
                                                        "message": "Your request is not valid, please correct and try again!",
                                                        "validationErrors": [{
                                                        "message": "Username should be minimum length of 3.",
                                                        "members": ["userName"]
                                                        },
                                                        {
                                                        "message": "Password is required",
                                                        "members": ["password"]
                                                        }]
                                                    }
                                                }

                                              AbpValidationException实现了IHasValidationErrors接口，当请求输入无效时，框架会自动抛出它。
                                              所以，通常你不需要处理验证错误，除非你有高度定制的验证逻辑。
                */

                /*
                    二、错误日志  Logging

                                捕获的异常会自动记录。

                                1、日志级别
                                        默认情况下，异常使用Error级别记录。
                                        如果异常实现了IHasLogLevel接口，则可以通过异常确定日志级别。
                                        例子：

                                        public class MyException : Exception, IHasLogLevel
                                        {
                                            public LogLevel LogLevel { get; set; } = LogLevel.Warning;

                                            //...
                                        }

                                2、自记录异常
                                        一些异常类型可能需要吸入额外的日志。
                                        如果需要，它们可以实现IExceptionWithSelfLogging 接口。

                                        例子：
                                        public class MyException : Exception, IExceptionWithSelfLogging
                                        {
                                            public void Log(ILogger logger)
                                            {
                                                //...log additional info
                                            }
                                        }   

                                        ILogger.LogException扩展方法用于编写异常日志。你可以在需要时使用相同的扩展方法。
                */


                /*
                    三、业务异常

                            你自己的大多数异常将是业务异常。
                            IBusinessException 接口用于将异常标记为业务异常。

                            BusinessException 除了 IHasErrorDetails 、IHasErrorDetails 和 IHasLogLevel 接口外，
                            还实现了IBusinessException接口。默认日志级别为 Warining.

                            通常你有一个与特定业务异常相关的错误代码。例如:

                                throw new BusinessException(QaErrorCodes.CanNotVoteYourOwnAnswer);

                            QaErrorCodes.CanNotVoteYourOwnAnswer只是一个const string。建议使用以下错误代码格式：
                               
                                <code-namespace>:<error-code>

                            code-namesspace是特定于你的模块/应用程序的唯一值。例子:

                                    Volo.Qa:010002
                            
                            Volo.Qa是这里的代码命名空间.代码命名空间将在本地化异常消息时使用。

                                    你可以在需要时直接抛出一个BusinessException或从中派生你自己的异常类型。

                                    BusinessException类的所有属性都是可选的。 

                                    但是你通常设置ErrorCode或Message属性。
                */

                /*
                    四、异常本地化
                            
                            抛出异常的一个问题是如何在将错误消息发送到客户端时对其进行本地化。

                            ABP提供了两种模型极其变体。

                                1、用户友好型异常：
                                        如果一个异常实现了 IUserFriendlyException 接口，那么ABP不会改变的 Message 和 Details ，并直接将其发送给客户端。
                                    
                                        UserFriendlyException 类是 IUserFriendlyException接口的内置实现。

                                        示例用法：
                                            throw new UserFriendlyException(
                                                "Username should be unique!"
                                            );

                                        这样，根本不需要本地化。

                                        如果要本地化消息，可以注入并使用标准的"字符串本地化器"：
                                            throw new UserFriendlyException(_stringLocalizer["UserNameShouldBeUniqueMessage"]);
                                        然后在"本地化资源"中为每种语言定义它。

                                        eg:
                                            {
                                                "culture": "en",
                                                "texts": {
                                                    "UserNameShouldBeUniqueMessage": "Username should be unique!"
                                                }
                                            }

                                        字符串本地化器已经支持参数化消息。
                                        
                                        例如：
                                            throw new UserFriendlyException(_stringLocalizer["UserNameShouldBeUniqueMessage", "john"]);

                                        那么它的本地化文本可以是：

                                            "UserNameShouldBeUniqueMessage": "Username should be unique! '{0}' is already taken!"

                                        IUserFriendlyException接口派生自IBusinessException，
                                        UserFriendlyException类派生自BusinessException类。    
                */


                /*
                    五、使用错误代码 ErrorCode

                                 UserFriendlyException很好，但是在高级用法中有一些问题：
                                    
                                    1、它要求你在任何地方注入字符串本地化器，并在抛出异常时始终使用它

                                    2、但是，在某些情况下，可能无法注入字符串本地化器（在静态上下文或实体方法中）。

                                 你可以使用错误代码分隔进程，而不是在抛出异常时本地化消息。

                                 首先，在模块配置中定义code-namesspace到本地化资源的 "映射"：
                                    
                                        services.Configure<AbpExceptionLocalizationOptions>(options =>
                                        {
                                            options.MapCodeNamespace("Volo.Qa", typeof(QaResource));
                                        });

                                然后，Volo.Qa命名空间的任何异常都将使用其给定的本地化资源进行本地化。
                                本地化资源应始终具有带有错误代码键的条目。
                                示例：
                                        {
                                            "culture": "en",
                                            "texts": {
                                                "Volo.Qa:010002": "You can not vote your own answer!"
                                            }
                                        }


                                然后可以使用错误代码抛出业务异常：

                                            throw new BusinessException(QaDomainErrorCodes.CanNotVoteYourOwnAnswer);

                                抛出任何实现IHasErrorCode接口的异常行为都是相同的。
                                因此，错误代码本地化方法并不是BusinessException类所独有的。

                                错误消息不需要定义本地化字符串。如果未定义，ABP将向客户端发送默认错误消息。它不使用异常的Message属性！
                                如果需要，请使用UserFriendlyException（或使用实现IUserFriendlyException接口的异常类型）。

                */
               
                /*
                    六、使用消息参数

                                如果你有参数化错误消息，则可以使用异常的Data属性对其进行设置。例如：

                                        throw new BusinessException("App:010046")
                                        {
                                            Data =
                                            {
                                                {"UserName", "john"}
                                            }
                                        }; 
                                        
                                幸运的是，有一种快捷方式可以对此进行编码：
                                       throw new BusinessException("App:010046")
                                            .WithData("UserName", "john");

                                然后本地化文本可以包含UserName参数：
                                        {
                                            "culture": "en",
                                            "texts": {
                                                "App:010046": "Username should be unique. '{UserName}' is already taken!"
                                            }
                                        }  
                                WithData可以用多个参数链接（如.WithData(...).WithData(...)）。  
                */

                /*
                    七、Http状态码映射
                                  
                    
                            ABP尝试通过遵循以下规则自动确定常见异常类型的最合适的HTTP状态代码：


                                        如果用户尚未登录，则返回401（未经授权）。

                                        如果用户已登录，则返回403（禁止）。

                                        返回AbpValidationException的400（错误请求）。

                                        返回EntityNotFoundException的404（未找到）。

                                        为IBusinessException返回403（禁止）。
                                        （和IUserFriendlyException，因为它扩展了IBusinessException）

                                        返回NotImplementedException的501（未实现）

                                        返回500（内部服务器错误）的其他异常（这些被假定为基础设施异常）。

                                        IHttpExceptionStatusCodeFinder用于自动确定HTTP状态码，默认实现为DefaultHttpExceptionStatusCodeFinder类，
                                        可以根据需要进行替换或扩展。
                */

                /*
                    八、自定义映射
                              自动HTTP状态码确定可以被自定义映射覆盖。例如：
                              
                                        services.Configure<AbpExceptionHttpStatusCodeOptions>(options =>
                                        {
                                            options.Map("Volo.Qa:010002", HttpStatusCode.Conflict);
                                        });
                */

                /*
                    九、订阅异常，并进行异常处理

                                当ABP处理异常时，你是可以得到通知的。
                                它会自动将所有异常记录到"标准日志记录器"，但是你可能需要做一些进一步的操作。

                                在这种情况下，在你的应用程序中创建一个从 ExceprionSubsciber(异常订阅类) 派生的类：
                                    public class MyExceptionSubscriber : ExceptionSubscriber
                                    {
                                        public async override Task HandleAsync(ExceptionNotificationContext context)
                                        {
                                            //TODO...
                                        }
                                    }

                                context对象包含有关发生的异常的必要信息。
                                 
                                你可以有多个订阅者，每个订阅者都获得异常的副本。
                                你的订阅者引发的异常将被忽略（但仍会记录）。
                */

                /*
                    十、ABP内置异常

                               AbpAuthorizationException。有关详细信息，请参阅授权。
                               
                               AbpValidationException。有关更多信息，请参阅验证。

                               EntityNotFoundException在请求的实体不可用时被抛出。这主要由存储库抛出。

                               你还可以在代码中抛出这些类型的异常（尽管很少需要）。
                */

                /*
                    十一、 ABP异常处理选项类  AbpExceptionHandlingOptions
                         
                            AbpExceptionHandlingOptions 是主要的选项对象来配置异常处理系统的。
                            你可以在模块的 ConfigureServices方法中配置它：

                                    Configure<AbpExceptionHandlingOptions>(options =>
                                    {
                                        options.SendExceptionsDetailsToClients = true;
                                        options.SendStackTraceToClients = false;
                                    }); 
                                    
                            在这里，你可以配置的选项列表：
                                ①、SendExceptionsDetailsToClients（默认值：false）：你可以启用或禁用向客户端发送异常详细信息。

                                ②、SendStackTraceToClients（默认值：true）：你可以启用或禁用向客户端发送异常的堆栈跟踪。
                                        如果要向客户端发送堆栈跟踪，你必须将SendStackTraceToClients和SendExceptionsDetailsToClients选项设置为true否则，
                                        堆栈跟踪将不会发送到客户端。
                */
        </script>
    </body>
</html>